/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.commands.connection;

import java.util.Iterator;
import java.util.Vector;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.uml2.uml.Element;

import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagraminterchange2.di2.Di2Factory;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;


/**
 * A base class to implements connection with anchors.
 */
public abstract class ConnectionCreateCommandBase extends ConnectionCommandBase {

	/**
	 * The owner of the connection.
	 */
	private GraphElement owner;

	/**
	 * Start endpoint for the connection.
	 */
	private final GraphElement source;

	/**
	 * Target endpoint for the connection.
	 */
	private GraphElement target;

	/**
	 * Point where the source is dropped.
	 */ 
	private Point sourcePos;

	/**
	 * Point where the target is dropped.
	 */ 
	private Point targetPos;

	/**
	 * Created edge.
	 */
	private GraphEdge edge;

	/**
	 * 
	 */
	protected Element umlLink;

	/**
	 * Instantiate a command that can create a connection between two shapes.
	 * The constructor takes the connection type and the ModelFactory in order to reduce the
	 * amount of computation each time a Command is created. The ConnectionFactory is searched only
	 * when the command is executed (instead of each time a Command is created).
	 * 
	 * @param owner 
	 * @param location 
	 * @param source 
	 * @param ressourceSet 	 * 
	 * @throws IllegalArgumentException if source is null
	 */
	public ConnectionCreateCommandBase(GraphElement owner, GraphElement source, Point location) {
		if (source == null || owner == null) {
			throw new IllegalArgumentException();
		}
		setLabel("connection creation");
		this.owner = owner;
		this.source = source;
		this.sourcePos = location;
	}

	/**
	 * Reconnect the bussiness model to the edge bridge.
	 * The source and target bridge are provided to help reconnection to the bussiness model.
	 * 
	 * @param bridge 
	 * @param targetBridge 
	 * @param sourceBridge 
	 */
	abstract public void reconnectBussinessModel( Uml1SemanticModelBridge bridge, Uml1SemanticModelBridge sourceBridge, Uml1SemanticModelBridge targetBridge);

	/**
	 * Disconnect the bussiness model from the edge bridge.
	 * The source and target bridge are provided to help disconnection to the bussiness model.
	 * 
	 * @param bridge 
	 * @param targetBridge 
	 * @param sourceBridge 
	 */
	abstract public void disconnectBussinessModel( Uml1SemanticModelBridge bridge, Uml1SemanticModelBridge sourceBridge, Uml1SemanticModelBridge targetBridge);

	/**
	 * Create the bussiness model.
	 * The created model should be stored by the implementation class for futur use (in reconnect/disconnect)
	 * 
	 * @return The created object.
	 */
	abstract public Element createBussinessModel( );

	/**
	 * Complete the bussiness model after the graphical creation.
	 * 
	 * @return The updated object.
	 */
	abstract public Object updateBusinessModel();

	/**
	 * Create the edge, its bridge and its anchors.
	 * Call the method to create the bussiness model. Don't store the bussiness model !
	 */
	public void execute() {
		// 1. Create the business model
		umlLink = createBussinessModel();

		// 2. Create edge
		edge = createGraphEdgeWithAnchors(umlLink);

		reconnectGraphEdgeAnchors(edge, owner, source, target);
		modifyBendpoint();
		// 3. Modify presentation, if necessary (enhancedType for example)
		updateBusinessModel();

		/*
		// Postpone the creation here in order to reduce the amount of computation each time 
		// a Command is created.
		edge = createGraphEdgeWithAnchors();
		modifyBendpoint();
		createBussinessModel();
		reconnectGraphEdgeAnchors(edge, owner, source, target);
		 */
	}

	/* 
	 * Reconnect the connection :
	 * Add the edge to its owner, add the anchor to their owner
	 * Call the method to reconnect the bussiness model. 
	 */
	/**
	 * 
	 */
	public void redo() {

//		// Disable notification
//		owner.eSetDeliver(false);
//		edge.eSetDeliver(false);
//		// attach the connection to its owner
//		edge.setContainer(owner);
//		// enable notification
//		owner.eSetDeliver(true);
//		edge.eSetDeliver(true);

		// create the property UML element

		// creates 2 properties; one in the source class, and one as member end of the association



		reconnectBussinessModel((Uml1SemanticModelBridge)edge.getSemanticModel(), (Uml1SemanticModelBridge)source.getSemanticModel(), (Uml1SemanticModelBridge)target.getSemanticModel());
		reconnectGraphEdgeAnchors(edge, owner, source, target);
	}


	/**
	 * Disconnect the connection.
	 * Disconnect the edge, its anchors and call the method to disconnect the bussiness model.
	 */
	public void undo() {
		disconnectGraphEdgeAnchors(edge);
		disconnectBussinessModel((Uml1SemanticModelBridge)edge.getSemanticModel(), (Uml1SemanticModelBridge)source.getSemanticModel(), (Uml1SemanticModelBridge)target.getSemanticModel());
		removeUmlLink(umlLink);
	}

	/**
	 * Set the target endpoint for the connection.
	 * 
	 * @param target that target endpoint (a non-null Shape instance)
	 * 
	 * @throws IllegalArgumentException if target is null
	 */
	public void setTarget(GraphElement target) {
		if (target == null) {
			throw new IllegalArgumentException();
		}
		this.target = target;
	}

	/**
	 * Set the target endpoint and its location.
	 * Called by the EditPolicy when the connectionCreate request is completed
	 * 
	 * @param target 
	 * @param location 
	 */
	public void setTarget(GraphElement target, Point location) {
		setTarget(target);
		targetPos = location;
	}

	/**
	 * Create a GraphEdge, its bridge and its anchors.
	 * 
	 * @param umlLink the element represented by the created link
	 * 
	 * @return The GraphEdge
	 */
	protected GraphEdge createGraphEdgeWithAnchors(Element umlLink) {
		GraphEdge node = createGraphEdge(umlLink);

		// create source anchor/connector
		GraphConnector srcConnector = Di2Factory.eINSTANCE.createGraphConnector();
		srcConnector.setPosition(sourcePos);
		// create target connector
		GraphConnector targetConnector = Di2Factory.eINSTANCE.createGraphConnector();
		targetConnector.setPosition(targetPos);

		// connect to graphEdge [0]=source  [1]=target
		node.getAnchor().add(srcConnector);
		node.getAnchor().add(targetConnector);
		return node;
	}

	/**
	 * Create a GraphEdge.
	 * This class can be overiden to provide other GraphEdge.
	 * 
	 * @param umlLink the element represented by the created link
	 * 
	 * @return The GraphEdge
	 */
	protected GraphEdge createGraphEdge(Element umlLink) {
		// create associated bridge
		Uml1SemanticModelBridge semantic= Di2Factory.eINSTANCE.createUml1SemanticModelBridge();
		// create GraphNode
		GraphEdge node = Di2Factory.eINSTANCE.createGraphEdge();
		node.setSemanticModel(semantic);
		semantic.setElement(umlLink);
		return node;
	}


	/**
	 * 
	 * 
	 * @param umlLink 
	 * 
	 * @return 
	 */
	protected boolean removeUmlLink(Element umlLink) {
		return false;
	}

	/**
	 * Get the associated model element from the edge.
	 * 
	 * @param element 	 * @param value 
	 */
	protected void setEdgeElement(Element value)
	{
		setBussinessElementFromGraphElement(edge, value);
	}

	/**
	 * Get the associated model element from the source.
	 * 
	 * @param element 	 * 
	 * @return 
	 */
	protected Element getEdgeElement()
	{
		return getBussinessElementFromGraphElement(edge);
	}

	/**
	 * Get the associated model element from the source.
	 * 
	 * @param element 	 * 
	 * @return 
	 */
	protected Element getSourceElement()
	{
		return getBussinessElementFromGraphElement(source);
	}

	/**
	 * Get the associated model element from the target.
	 * 
	 * @param element 	 * @param value 
	 */
	protected void setSourceElement(Element value)
	{
		setBussinessElementFromGraphElement(source, value);
	}


	/**
	 * Get the associated model element from the target.
	 * 
	 * @param element 	 * 
	 * @return 
	 */
	protected Element getTargetElement()
	{
		return getBussinessElementFromGraphElement(target);
	}

	/**
	 * Get the associated model element from the target.
	 * 
	 * @param element 	 * @param value 
	 */
	protected void setTargetElement(Element value)
	{
		setBussinessElementFromGraphElement(target, value);
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	public GraphEdge getEdge() {
		return edge;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	public GraphElement getOwner() {
		return owner;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	public Element getUMLParent(){
		try {
			return LookForElement.lookForUMLElementOf(owner);
		} catch (Exception e) {
			Debug.log(e);
		}
		return null;
	}
	
	

	/**
	 * 
	 * 
	 * @return 
	 */
	public GraphElement getSource() {
		return source;
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	public GraphElement getTarget() {
		return target;
	}

	/**
	 * 
	 * 
	 * @param owner 
	 */
	public void setOwner(GraphElement owner) {
		this.owner = owner;
	}


	/**
	 * this methode is used to replace bendpoint of the link 
	 * for example when the target and the source of the link is the same 
	 * element
	 */
	public void modifyBendpoint(){
		int distance = 30;
		if ((getSource() instanceof GraphNode) && (getTarget() instanceof GraphNode) && (getSource() == getTarget())) {
			// the link is on the same object
			Rectangle bounds= new Rectangle(((GraphNode)getSource()).getPosition(),((GraphNode)getSource()).getSize());
			Rectangle recInside = bounds.crop(new Insets(0));
			GraphConnector src = getEdgeAnchor(getEdge(), SOURCE);
			GraphConnector trg = getEdgeAnchor(getEdge(),TARGET );

			int posSrc=recInside.getPosition(src.getPosition());
			int postrg=recInside.getPosition(trg.getPosition());

			//a vector to collect all bend oints to be added
			Vector<Point> pointsToAdd = new Vector<Point>();
			
			//deal with the case when anchor points are inside the rectangle
			//defaulted as a NORTH to EAST case connection
			if((posSrc == PositionConstants.NONE) || (postrg == PositionConstants.NONE)){
				posSrc= PositionConstants.NORTH;
				postrg = PositionConstants.EAST;
			}
			
			//this is the same side
			if(posSrc == postrg){
				if(posSrc == PositionConstants.EAST){
					pointsToAdd.add(new Point(src.getPosition().x+distance, src.getPosition().y));
					pointsToAdd.add(new Point(trg.getPosition().x+distance, trg.getPosition().y));
				}
				if(posSrc == PositionConstants.WEST){
					pointsToAdd.add(new Point(src.getPosition().x-distance, src.getPosition().y));
					pointsToAdd.add(new Point(trg.getPosition().x-distance, trg.getPosition().y));
				}
				if( posSrc == PositionConstants.SOUTH){
					pointsToAdd.add(new Point(src.getPosition().x, src.getPosition().y+distance));
					pointsToAdd.add(new Point(trg.getPosition().x, trg.getPosition().y+distance));
				}
				if(posSrc == PositionConstants.NORTH){
					pointsToAdd.add(new Point(src.getPosition().x, src.getPosition().y-distance));
					pointsToAdd.add(new Point(trg.getPosition().x, trg.getPosition().y-distance));
				}
			}
			//case in which we calculate three positions A,B,C
			else if(posSrc==PositionConstants.NORTH && postrg==PositionConstants.WEST||
					posSrc==PositionConstants.WEST && postrg==PositionConstants.NORTH){
				//compute all coordinates
				Point A;
				Point B;
				Point C;
				//adding in the direct sens
				if(posSrc==PositionConstants.NORTH && postrg==PositionConstants.WEST){
					A = new Point(src.getPosition().x, src.getPosition().y-distance);
					B = new Point(trg.getPosition().x-distance, src.getPosition().y-distance);
					C = new Point(trg.getPosition().x-distance, trg.getPosition().y);
				}
				else{
					//WEST to NORTH
					//adding in the inverse sens
					A = new Point(src.getPosition().x-distance, src.getPosition().y);
					B = new Point(src.getPosition().x-distance, trg.getPosition().y-distance);
					C = new Point(trg.getPosition().x, trg.getPosition().y-distance);
				}
				pointsToAdd.add(A);
				pointsToAdd.add(B);
				pointsToAdd.add(C);
			}
			else if(posSrc==PositionConstants.WEST && postrg==PositionConstants.SOUTH||
					posSrc==PositionConstants.SOUTH && postrg==PositionConstants.WEST){
				Point A;
				Point B;
				Point C;
				if(posSrc==PositionConstants.WEST && postrg==PositionConstants.SOUTH){
					A = new Point(src.getPosition().x-distance, src.getPosition().y);
					B = new Point(src.getPosition().x-distance, trg.getPosition().y+distance);
					C = new Point(trg.getPosition().x, trg.getPosition().y+distance);
				}
				else{
					//SOUTH to WEST
					//adding in the inverse sens
					A = new Point(src.getPosition().x, src.getPosition().y+distance);
					B = new Point(trg.getPosition().x-distance, src.getPosition().y+distance);
					C = new Point(trg.getPosition().x-distance, trg.getPosition().y);
				}
				pointsToAdd.add(A);
				pointsToAdd.add(B);
				pointsToAdd.add(C);
			}	
			else if(posSrc==PositionConstants.SOUTH && postrg==PositionConstants.EAST||
					posSrc==PositionConstants.EAST && postrg==PositionConstants.SOUTH){
				Point A;
				Point B;
				Point C;
				if(posSrc==PositionConstants.SOUTH && postrg==PositionConstants.EAST){
					A = new Point(src.getPosition().x, src.getPosition().y+distance);
					B = new Point(trg.getPosition().x+distance, src.getPosition().y+distance);
					C = new Point(trg.getPosition().x+distance, trg.getPosition().y);

				}
				else{
					//EAST to SOUTH
					//adding in the inverse sens
					A = new Point(src.getPosition().x+distance, src.getPosition().y);
					B = new Point(src.getPosition().x+distance, trg.getPosition().y+distance);
					C = new Point(trg.getPosition().x, trg.getPosition().y+distance);
				}
				pointsToAdd.add(A);
				pointsToAdd.add(B);
				pointsToAdd.add(C);
			}	
			else if(posSrc==PositionConstants.EAST && postrg==PositionConstants.NORTH||
					posSrc==PositionConstants.NORTH && postrg==PositionConstants.EAST){
				Point A;
				Point B;
				Point C;
				if(posSrc==PositionConstants.EAST && postrg==PositionConstants.NORTH){
					A = new Point(src.getPosition().x+distance, src.getPosition().y);
					B = new Point(src.getPosition().x+distance, trg.getPosition().y-distance);
					C = new Point(trg.getPosition().x, trg.getPosition().y-distance);
				}
				else{
					//NORTH to EAST
					//adding in the inverse sens
					A = new Point(src.getPosition().x, src.getPosition().y-distance);
					B = new Point(trg.getPosition().x+distance, src.getPosition().y-distance);
					C = new Point(trg.getPosition().x+distance, trg.getPosition().y);
				}
				pointsToAdd.add(A);
				pointsToAdd.add(B);
				pointsToAdd.add(C);
			}	
			//case in which we calculate 4 positions: A,B,C,D
			else{
				//in this case there are only 4 cases.
				Point A;
				Point B;
				Point C;
				Point D;
				if(posSrc==PositionConstants.WEST && postrg==PositionConstants.EAST){
					A = new Point(src.getPosition().x-distance, src.getPosition().y);
					B = new Point(src.getPosition().x-distance, bounds.getBottomLeft().y+distance);
					C = new Point(trg.getPosition().x+distance, bounds.getBottomRight().y+distance);
					D = new Point(trg.getPosition().x+distance, trg.getPosition().y);

				}
				else if(posSrc==PositionConstants.EAST && postrg==PositionConstants.WEST){
					A = new Point(src.getPosition().x+distance, src.getPosition().y);
					B = new Point(src.getPosition().x+distance, bounds.getBottomRight().y+distance);
					C = new Point(trg.getPosition().x-distance, bounds.getBottomLeft().y+distance);
					D = new Point(trg.getPosition().x-distance, trg.getPosition().y);
				}
				else if(posSrc==PositionConstants.NORTH && postrg==PositionConstants.SOUTH){
					A = new Point(src.getPosition().x, src.getPosition().y-distance);
					B = new Point(bounds.getTopRight().x+distance, src.getPosition().y-distance);
					C = new Point(bounds.getBottomRight().x+distance, trg.getPosition().y+distance);
					D = new Point(trg.getPosition().x, trg.getPosition().y+distance);	
				}
				else{
					A = new Point(src.getPosition().x, src.getPosition().y+distance);
					B = new Point(bounds.getBottomRight().x+distance, src.getPosition().y+distance);
					C = new Point(bounds.getTopRight().x+distance, trg.getPosition().y-distance);
					D = new Point(trg.getPosition().x, trg.getPosition().y-distance);
				}
				pointsToAdd.add(A);
				pointsToAdd.add(B);
				pointsToAdd.add(C);
				pointsToAdd.add(D);
			}	
			
			//a loop to cope with links within container
			//ion this case we need to translate the bendpoints according to the containment hierarchy
			GraphElement currentOwner = owner;
			//an initial little slide of 1
			if(!(currentOwner instanceof Diagram)){
				Iterator it = pointsToAdd.iterator();
				while(it.hasNext())
					((Point)it.next()).translate(1, 1);
			}
			//then a chain of translations
			while(!(currentOwner instanceof Diagram)){
				Iterator it = pointsToAdd.iterator();
				while(it.hasNext()){
					((Point)it.next()).translate(currentOwner.getPosition());
				}
				currentOwner = currentOwner.getContainer();
			}
			//finally we add the point list to the Waypoint list of the edge
			//this triggers the refresh
			Iterator it = pointsToAdd.iterator();
			while(it.hasNext())
				getEdge().getWaypoints().add((Point)it.next());
		}
	}
}
